package org.budo.support.lang.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.budo.support.assertion.Assert;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.core.DefaultParameterNameDiscoverer;

/**
 * @author limingwei
 * @date 2016年6月16日 下午4:00:06
 */
public class ReflectUtil {
    private static Logger log = Slf4j.getLogger();

    private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();

    public static Boolean hasField(Class<?> type, String field) {
        try {
            type.getDeclaredField(field);// 有此属性
            return true;
        } catch (Exception e) {
            Class<?> superType = type.getSuperclass();
            if (superType.equals(Object.class)) {
                return false;
            }
            return hasField(superType, field);
        }
    }

    public static Class<?> getActualTypeArgument(Class<?> type, Integer index) {
        Type[] actualTypeArguments = getActualTypeArguments(type);
        if (null == actualTypeArguments || actualTypeArguments.length < 1) {
            return null;
        }

        Type actualType = actualTypeArguments[index];
        if (actualType instanceof ParameterizedType) { // Set<String> -> Set.class
            return (Class<?>) ((ParameterizedType) actualType).getRawType();
        }

        return (Class<?>) actualType;
    }

    public static Type[] getActualTypeArguments(Class<?> type) {
        Type genericSuperclass = type.getGenericSuperclass();
        if (null == genericSuperclass || !(genericSuperclass instanceof ParameterizedType)) {
            return null;
        }
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        return parameterizedType.getActualTypeArguments();
    }

    public static Field getField(Class<?> targetType, String name) {
        try {
            return targetType.getDeclaredField(name);
        } catch (Throwable e) {
            if (targetType.equals(Object.class)) {
                throw new RuntimeException(e);
            }

            return getField(targetType.getSuperclass(), name);
        }
    }

    private static void makeAccessible(Field field) {
        Field modifiersField = getField(Field.class, "modifiers");
        modifiersField.setAccessible(true);

        try {
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        } catch (Throwable e) {
            log.error("#83 field makeAccessible error, field=" + field + ", e=" + e, e);
        }

        field.setAccessible(true);
    }

    public static void setFieldValue(Object object, Field field, Object value) {
        try {
            makeAccessible(field);
            field.set(object, value);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static void setFieldValue(Object object, String fieldName, Object value) {
        Field field = getField(object.getClass(), fieldName);
        if (field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
        }

        setFieldValue(object, field, value);
    }

    /**
     * 获取静态属性(常量)的值
     */
    public static Object getFieldValue(Field field) {
        try {
            makeAccessible(field);
            return field.get(null);
        } catch (Throwable e) {
            throw new RuntimeException("#103 getFieldValue error, e=" + e + ", field=" + field);
        }
    }

    public static Object getFieldValue(Class<?> targetType, String fieldName, Object target) {
        try {
            Field field = getField(targetType, fieldName);
            makeAccessible(field);
            return field.get(target);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static Object getFieldValue(Class<?> targetType, String fieldName) {
        try {
            Field field = getField(targetType, fieldName);
            makeAccessible(field);
            return field.get(null);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static Object getFieldValue(Object object, String fieldName) {
        Class<?> type = object.getClass();

        Field field = getField(type, fieldName);
        if (field == null) {
            throw new IllegalArgumentException("#112 Could not find field [" + fieldName + "] on target [" + object + "]");
        }

        try {
            makeAccessible(field);
            return field.get(object);
        } catch (Throwable e) {
            throw new RuntimeException("#119 getFieldValue error, e=" + e + ", field=" + field);
        }
    }

    public static List<Method> getAllMethods(Class<?> type) {
        if (null == type) {
            return null;
        }

        List<Method> methods = new ArrayList<Method>();
        Method[] declaredMethods = type.getDeclaredMethods();
        List<Method> declaredMethodList = ListUtil.toList(declaredMethods);
        methods.addAll(declaredMethodList);

        if (!Object.class.equals(type)) {
            Class<?> superType = type.getSuperclass();
            List<Method> superMethods = getAllMethods(superType);
            ListUtil.addAll(methods, superMethods);
        }
        return methods;
    }

    @SuppressWarnings("rawtypes")
    public static Class classForName(String type) {
        if (null == type || type.isEmpty()) {
            return null;
        }

        try {
            return Class.forName(type);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static List<Class<?>> getAllInterfaces(Class<?> beanType) {
        List<Class<?>> list = new ArrayList<Class<?>>();
        Class<?>[] interfaceTypes = beanType.getInterfaces();
        for (Class<?> type : interfaceTypes) {
            list.add(type);
        }

        Class<?> superType = beanType.getSuperclass();
        if (!Object.class.equals(superType)) {
            List<Class<?>> superInterfaces = getAllInterfaces(superType);
            list.addAll(superInterfaces);
        }
        return list;
    }

    public static Boolean isInterface(String typeName) {
        Class type = classForName(typeName);
        return type.isInterface();
    }

    public static Boolean hasClass(String typeName) {
        try {
            Class.forName(typeName);
            return true;
        } catch (Throwable e) {
            return false;
        }
    }

    public static List<Field> getAllFields(Class<?> type) {
        List<Field> fields = new ArrayList<Field>();
        Field[] declaredFields = type.getDeclaredFields();
        Collections.addAll(fields, declaredFields);

        Class<?> superclass = type.getSuperclass();
        if (null != superclass && Object.class != superclass) {// 扫描超类的Field
            List<Field> superFields = getAllFields(superclass);
            fields.addAll(superFields);
        }

        return fields;
    }

    public static Object newInstance(Class<?> type) {
        try {
            return type.newInstance();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static <T extends Annotation> T getAnnotation(Class<?> targetType, Class<T> annotationType) {
        if (null == targetType || null == annotationType) {
            return null;
        }

        T annotation = targetType.getAnnotation(annotationType);
        if (null != annotation) {
            return annotation;
        }

        if (!targetType.equals(Object.class)) {
            return getAnnotation(targetType.getSuperclass(), annotationType);
        }

        return null;
    }

    public static void setFieldValue(Class<?> targetType, String fieldName, Object target) {
        try {
            Field field = getField(targetType, fieldName);
            makeAccessible(field);
            field.set(null, target);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 如果方法不存在，就抛NoSuchMethodException
     */
    public static Method getMethod(Class<?> targetType, String name, Class<?>[] parameterTypes) {
        String methodCacheKey = makeMethodString(targetType, name, parameterTypes);
        if (METHOD_CACHE.containsKey(methodCacheKey)) {
            return METHOD_CACHE.get(methodCacheKey);
        }

        try {
            Method method = targetType.getMethod(name, parameterTypes);
            METHOD_CACHE.put(methodCacheKey, method);
            return method;
        } catch (Exception e) {
            throw new RuntimeException("#233 targetType=" + targetType + ", name=" + name, e);
        }
    }

    /**
     * @return 如果方法不存在，就返回null
     */
    public static Method findMethod(Class<?> targetType, String name, Class<?>[] parameterTypes) {
        String methodCacheKey = makeMethodString(targetType, name, parameterTypes);
        if (METHOD_CACHE.containsKey(methodCacheKey)) {
            return METHOD_CACHE.get(methodCacheKey);
        }

        try {
            Method method = targetType.getMethod(name, parameterTypes);
            METHOD_CACHE.put(methodCacheKey, method);
            return method;
        } catch (NoSuchMethodException e) {
            return null;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 返回方法形参名列表，有可能会取不到
     */
    public static String[] getMethodParameterNames(Method method) {
        if (null == method) {
            return null;
        }

        String[] parameterNames = _getMethodParameterNames(method); // 递归调用
        if (null != parameterNames) {
            return parameterNames;
        }

        Class<?> declaringType = method.getDeclaringClass();
        if (declaringType.equals(Object.class)) { // Object 的方法就不打日志
            return null;
        }

        String declaringTypeName = declaringType.getName();
        if (declaringTypeName.startsWith("com.alibaba.dubbo.common.bytecode.proxy")) {
            return null;
        }

        // 打日志并返回空
        log.error("#203 getMethodParameterNames error, method=" + method + ", parameterNames=" + parameterNames);
        return null;
    }

    /**
     * 私有方法,递归调用
     */
    private static String[] _getMethodParameterNames(Method method) {
        DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);

        if (null != parameterNames) {
            return parameterNames;
        }

        Class<?> superType = method.getDeclaringClass().getSuperclass();
        if (null != superType && !superType.equals(Object.class)) {
            Method superMethod = ReflectUtil.getMethod(superType, method.getName(), method.getParameterTypes());
            String[] superMethodParameterNames = _getMethodParameterNames(superMethod); // 递归调用
            return superMethodParameterNames;
        }

        return null;
    }

    public static Object invokeMethod(Object target, Method method, Object[] args) {
        try {
            return method.invoke(target, args);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 调用标准的get方法
     */
    public static <T> T invokeGet(Object obj, String fieldName) {
        Assert.notNull(obj, "obj must not be null");
        Assert.notNull(fieldName, "fieldName must not be null");

        String methodName = StringUtil.buildGet(fieldName);
        Method method = findMethod(obj.getClass(), methodName, null);
        return (T) invokeMethod(obj, method, null);
    }

    /**
     * 调用标准的set方法
     */
    public static <T> void invokeSet(T obj, Field field, Object param) {
        Assert.notNull(obj, "obj must not be null");
        Assert.notNull(field, "field must not be null");

        Class<?>[] classes = new Class<?>[] { field.getType() };

        String methodName = StringUtil.buildSet(field.getName());
        Method method = findMethod(obj.getClass(), methodName, classes);

        invokeMethod(obj, method, new Object[] { param });
    }

    public static boolean hasMethod(Class<?> targetType, String name, Class[] parameterTypes) {
        return null != findMethod(targetType, name, parameterTypes);
    }

    public static Boolean isPrimitiveTypes(Class<?> type) {
        if (null == type) {
            return false;
        }

        return type.isPrimitive() //
                || String.class.equals(type) //
                || Boolean.class.equals(type) //
                || Character.class.equals(type) //
                || Number.class.isAssignableFrom(type);
    }

    public static Boolean isBasicType(Class<?> type) {
        if (null == type) {
            return false;
        }

        return isPrimitiveTypes(type) || Date.class.isAssignableFrom(type);
    }

    public static boolean isNumberType(Class<?> type) {
        if (null == type) {
            return false;
        }

        return Number.class.isAssignableFrom(type) //
                || byte.class.equals(type) //
                || short.class.equals(type) //
                || int.class.equals(type) //
                || long.class.equals(type) //
                || float.class.equals(type) //
                || double.class.equals(type);
    }

    /**
     * @see Method#toGenericString()
     */
    public static String methodToString(Method method) {
        if (null == method) {
            return "<method-is-null>";
        }

        try {
            StringBuilder stringBuilder = new StringBuilder();

            stringBuilder.append(method.getDeclaringClass().getName()).append('.');
            stringBuilder.append(method.getName()).append('(');
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                Class<?> parameterType = parameterTypes[j];

                String parameterTypeString;
                if (parameterType.isArray()) {
                    parameterTypeString = parameterType.getComponentType().getCanonicalName() + "...";
                } else {
                    parameterTypeString = parameterType.getCanonicalName();
                }
                stringBuilder.append(parameterTypeString);

                if (j < (parameterTypes.length - 1)) {
                    stringBuilder.append(',');
                }
            }
            stringBuilder.append(')');
            return stringBuilder.toString();
        } catch (Throwable e) {
            log.error("#357 methodToString error, e=" + e, e);
            return "<" + e + ">";
        }
    }

    public static String makeMethodString(Class<?> targetType, String name, Class<?>[] parameterTypes) {
        Assert.notNull(targetType, "#2234 targetType must not be null");
        Assert.notNull(name, "#2235 name must not be null");

        StringBuilder result = new StringBuilder();
        result.append(targetType.getCanonicalName()).append(".").append(name).append("(");
        if (parameterTypes != null && parameterTypes.length > 0) {
            for (int i = 0; i < parameterTypes.length; i++) {
                result.append(parameterTypes[i].getCanonicalName());
                if (i != parameterTypes.length - 1) {
                    result.append(",");
                }
            }
        }
        result.append(")");

        return result.toString();
    }

    /**
     * 传入一个（类的）方法，返回此类实现的接口上的对应方法
     */
    public static List<Method> findInterfaceMethods(Method method) {
        if (null == method) {
            return null;
        }

        Class<?> declaringType = method.getDeclaringClass();
        Class<?>[] interfaceTypes = declaringType.getInterfaces(); // 似乎返回的是直接接口，间接的未返回

        List<Method> interfaceMethods = new ArrayList<Method>();
        if (null != interfaceTypes) {
            for (Class<?> interfaceType : interfaceTypes) {
                Method interfaceMethod = ReflectUtil.findMethod(interfaceType, method.getName(), method.getParameterTypes());
                if (null != interfaceMethod) {
                    interfaceMethods.add(interfaceMethod);
                }
            }
        }

        return interfaceMethods;
    }

    /**
     * 传入一个（类的）方法，返回此类继承的超类上的对应方法
     */
    public static List<Method> findSuperMethods(Method method) {
        if (null == method) {
            return null;
        }

        Class<?> declaringType = method.getDeclaringClass();
        Class<?> superType = declaringType.getSuperclass(); // 似乎返回的是直接接口，间接的未返回

        List<Method> superMethods = new ArrayList<Method>();
        if (null != superType) {
            Method interfaceMethod = ReflectUtil.findMethod(superType, method.getName(), method.getParameterTypes());
            if (null != interfaceMethod) {
                superMethods.add(interfaceMethod);
            }
        }

        return superMethods;
    }

    public static boolean hasAnnotationPresent(Class clazz, Class<? extends Annotation> annoClass) {
        Assert.notNull(clazz, "clazz must not be null");
        Assert.notNull(annoClass, "annoClass must not be null");

        if (!clazz.isAnnotationPresent(annoClass)) {
            return false;
        }
        return true;
    }

    public static boolean hasAnnotationPresent(Field field, Class<? extends Annotation> annoClass) {
        Assert.notNull(field, "field must not be null");
        Assert.notNull(annoClass, "annoClass must not be null");
        if (!field.isAnnotationPresent(annoClass)) {
            return false;
        }
        return true;
    }

    /**
     * 查询类的所有属性的集合
     */
    public static Set<String> getAllAttribute(Class<?> clazz) {
        Assert.notNull(clazz, "clazz must not be null");

        Set<String> attributes = new HashSet<>();

        List<Field> allFields = getAllFields(clazz);
        if (!CollectionUtil.isEmpty(allFields)) {
            for (Field field : allFields) {
                attributes.add(field.getName());
            }
        }

        return attributes;
    }

    /**
     * 判断某个类中是否存在某个属性被某个注解注释
     */
    public static boolean hasFieldAnnotationBy(Class<?> clazz, Class<? extends Annotation> annotation) {
        Assert.notNull(clazz, "clazz must not be null");
        Assert.notNull(annotation, "annotation must not be null");

        List<Field> allFields = getAllFields(clazz);
        if (CollectionUtil.isEmpty(allFields)) {
            return false;
        }

        for (Field field : allFields) {
            if (hasAnnotationPresent(field, annotation)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 是否是静态属性
     */
    public static boolean isStatic(Field field) {
        Assert.notNull(field, "field must not be null");
        return Modifier.isStatic(field.getModifiers());
    }

    /**
     * 是否是final属性
     */
    public static boolean isFinal(Field field) {
        Assert.notNull(field, "field must not be null");
        return Modifier.isFinal(field.getModifiers());
    }

    /**
     * 返回指定类型上的所有常量
     */
    public static Map<String, Object> constantValues(Class<?> type) {
        if (null == type) {
            return null;
        }

        Map<String, Object> map = new HashMap<String, Object>();

        List<Field> fields = getAllFields(type);
        for (Field field : fields) {
            String name = field.getName();
            Object value = getFieldValue(field);

            map.put(name, value);
        }

        return map;
    }
}
